# -*- python -*-
# ex: set syntax=python:

# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import os
import random
import shutil
import subprocess
import sys
import tempfile

# These modules come from scripts/master, which must be in the PYTHONPATH.
from common import cros_chromite
from master import master_utils
from master import slaves_list
from master.cros_try_job_git import CrOSTryJobGit
from master.factory import chromeos_factory

from buildbot.buildslave import BuildSlave
from buildbot.changes.gitpoller import GitPoller
from buildbot.process.properties import WithProperties

# These modules come from scripts/common, which must be in the PYTHONPATH.
import chromiumos_tryserver_util
import config
import master_site_config

ActiveMaster = master_site_config.ChromiumOSTryServer

# This is the dictionary that the buildmaster pays attention to. We also use
# a shorter alias to save typing.
c = BuildmasterConfig = {}

config.DatabaseSetup(c, require_dbconfig=ActiveMaster.is_production_host)

####### CHANGESOURCES

# The gitpoller doesn't play well with SSH-AGENT, so for test masters running
# on a developer's machine, you need to set up a password-less ssh key.
c['change_source'] = []
c['change_source'].append(GitPoller(
    repourl=ActiveMaster.repo_url_ext,
    branch='master' if ActiveMaster.is_production_host else 'test',
    workdir=tempfile.mkdtemp(prefix='gitpoller_ext'),
    pollinterval=10))

if ActiveMaster.repo_url_int:
  c['change_source'].append(GitPoller(
      repourl=ActiveMaster.repo_url_int,
      branch='master' if ActiveMaster.is_production_host else 'test',
      workdir=tempfile.mkdtemp(prefix='gitpoller_int'),
      pollinterval=10))

# Avoid merging requests.
c['mergeRequests'] = lambda *_: False


####### JOB AND BUILDER SELECTION ALGORITHM

DEFINED_SLAVES = slaves_list.SlavesList('slaves.cfg', 'ChromiumOSTryServer')

# We isolate testing slaves here - we can force their use through the try job.
testing_slave_pool = chromiumos_tryserver_util.TestingSlavePool(
    [slave['hostname']
     for slave in DEFINED_SLAVES.GetSlaves()
     if slave['version'] == 'testing'])

####### BUILDERS

c['builders'] = []

# ----------------------------------------------------------------------------
# BUILDER DEFINITIONS

nextSlaveAndBuild = chromiumos_tryserver_util.NextSlaveAndBuild(
    testing_slave_pool=testing_slave_pool)

# The 'builders' list defines the Builders. Each one is configured with a
# dictionary, using the following keys:
#  name (required): the name used to describe this bilder
#  slavename (required): which slave to use, must appear in c['slaves']
#  builddir (required): which subdirectory to run the builder in
#  factory (required): a BuildFactory to define how the build is run
#  category (optional): it is not used in the normal 'buildbot' meaning. It is
#                       used by gatekeeper to determine which steps it should
#                       look for to close the tree.
def _GetCBuildbotBuilder(name, category, cbuilddir, params=None, clobber=False):

  buildroot = os.path.join('/b/cbuild', cbuilddir)
  factory = chromeos_factory.CbuildbotFactory(
      params=params,
      trybot=True,
      buildroot=buildroot,
      clobber=clobber).get_factory()

  return {
      'builddir': name.replace(' ', '-'),
      'category': category,
      'factory': factory,
      'name': name,
      'slavenames': DEFINED_SLAVES.GetSlavesName(builder=name),
      'nextSlaveAndBuild' : nextSlaveAndBuild,
  }


def _GetCategory(cfg):
  if cfg in ('etc',):
    return '1etc full|info'
  elif isinstance(cfg, cros_chromite.ChromiteTarget):
    if cfg.IsGeneralPreCqBuilder():
      return '2general-precq full|info'
    elif cfg.IsPreCqBuilder():
      return '3precq full|info'
    return '4release full|info'
  else:
    raise TypeError("Unhandled builder type: %s" % (type(cfg).__name__))


def _GetBuilder(cfg):
  cbuilddir = '%(type)s_%(branch)s' % {
      'type': 'internal' if cfg.get('internal') else 'external',
      'branch': 'master'}
  builder = _GetCBuildbotBuilder(
      cfg.name,
      _GetCategory(cfg),
      cbuilddir,
      params=cfg.name,
  )
  return builder


def _GetEtcBuilder():
  name = 'etc'
  cbuilddir = 'etc_%(branch)s' % {
      'branch': 'master',
  }

  builder = _GetCBuildbotBuilder(
      name,
      _GetCategory(name),
      cbuilddir,
      params=WithProperties('%(chromeos_config:-)s'),
      clobber=True,
  )
  return builder


# Add 'etc' builder to try arbitrary configs.
c['builders'] += [_GetEtcBuilder()]

# Add Try builders for every current 'cbuildbot' config.
c['builders'] += [_GetBuilder(cfg)
                  for cfg in chromiumos_tryserver_util.configs.itervalues()]

# Sort builders by category, then name.
c['builders'].sort(key=lambda b: b['category'])

####### BUILDSLAVES

# the 'slaves' list defines the set of allowable buildslaves. Each element is a
# tuple of bot-name and bot-password. These correspond to values given to the
# buildslave's mktap invocation.
c['slaves'] = master_utils.AutoSetupSlaves(c['builders'],
                                           config.Master.GetBotPassword())
assert c['slaves'], "Failed to apply slave configuration!"

####### SCHEDULERS

smtp_host = config.Master.smtp if ActiveMaster.is_production_host else 'smtp'
email_footer = """
<strong>Please send bugs and questions to %(reply_to)s.  You can
also reply to this email.</strong>
""" % {'reply_to' : ActiveMaster.reply_to}
c['schedulers'] = []
c['schedulers'].append(CrOSTryJobGit(
    name='cros_try_job_git',
    pollers=c['change_source'],
    smtp_host=smtp_host,
    from_addr=ActiveMaster.from_address,
    reply_to=ActiveMaster.reply_to,
    email_footer=email_footer,
    cbuildbot_configs=[cfg for cfg in chromiumos_tryserver_util.cbb_builders],
    etc_builder='etc',
))


####### STATUS TARGETS

# Buildbot master url:
# Must come before AutoSetupMaster().
c['buildbotURL'] = ActiveMaster.buildbot_url

# Adds common status and tools to this master.
web_template_globals = {
    'cros_slave_name': testing_slave_pool.cros_slave_name,
    'cros_builder_links': chromiumos_tryserver_util.cros_builder_links,
}
master_utils.AutoSetupMaster(c, ActiveMaster, order_console_by_time=True,
                             public_html='../master.chromium/public_html',
                             templates=[ '../master.chromiumos/templates',
                                         '../master.chromium/templates'],
                             web_template_globals=web_template_globals,
                             )

# Add a dumb MailNotifier first so it will be used for BuildSlave with
# notify_on_missing set when they go missing.
from buildbot.status import mail
c['status'].append(mail.MailNotifier(
    fromaddr=ActiveMaster.from_address,
    builders=[],
    relayhost=config.Master.smtp,
    lookup=master_utils.UsersAreEmails()))

# Try job result emails.
from master.try_mail_notifier import TryMailNotifier

c['status'].append(TryMailNotifier(
    reply_to=ActiveMaster.reply_to,
    failure_message='TRY FAILED',
    footer=email_footer,
    fromaddr=ActiveMaster.from_address,
    subject="try %(result)s for %(reason)s on %(builder)s",
    mode='all',
    relayhost=smtp_host,
    lookup=master_utils.UsersAreEmails()))
